home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / DBL Pascal Library / HashTables ƒ / HashTables.p < prev   
Text File  |  1993-10-25  |  27KB  |  783 lines

  1. unit HashTables;
  2.  
  3. {Copyright © 1993 David B. Lamkins. All rights reserved worldwide.}
  4. {}
  5. {This implementation of hash tables uses three handles: one for the table, one for the}
  6. {buckets, and one for all the keys and values. None of these handles are locked except}
  7. {during operations which call user-provided functions for hashing and matching keys.}
  8. {}
  9. {Nominal overhead is 50 bytes plus 4 bytes per slot for the table, 12 bytes for each}
  10. {bucket, and the Memory Manager overhead for three relocatable blocks. Actual overhead}
  11. {will be higher due to preallocation of space to store buckets and data.}
  12. {}
  13. {The buckets area is preallocatated to hold as many entries as there are slots. Deleted}
  14. {buckets are returned to a free list for reuse. Each time all free buckets are used up, the}
  15. {area is expanded to hold about half as many extra buckets as there are slots in the table.}
  16. {The data area is also preallocated at a reasonable size, depending upon the number of slots}
  17. {and the expected average size of entries. When the data area fills up, it is extended by}
  18. {a reasonable amount to allow room for extra entries. This strategy of allocating extra}
  19. {space for data and bucket areas minimizes the heap thrashing that might otherwise}
  20. {occur if an area was expanded only to hold the next entry.}
  21. {}
  22. {Values are stored in-place in the data area. Replacing a value with one of a different size}
  23. {is equivalent to deleting the old entry then adding a new one. This is very fast, but it has}
  24. {an impact on the space used for key/value storage — see below. You can avoid the deletion}
  25. {overhead by replacing an existing value with a new value of identical size.}
  26. {}
  27. {Deletion of an entry leaves unused gaps which must be compacted out of the data area if}
  28. {their total size becomes excessive.}
  29. {}
  30. {Compaction is an expensive operation taking time proportional to the amount of space used}
  31. {by all the keys and values, but is more efficient than the multiple block moves that would}
  32. {be needed to remove the space unused by deleted entries when done one at a time.}
  33.  
  34. interface
  35.  
  36.     type
  37.         HashTable = Handle;
  38.  
  39.     const    {Some handy prime numbers to use for table sizes.}
  40.         kPrime67 = 67;
  41.         kPrime139 = 139;
  42.         kPrime281 = 281;
  43.         kPrime563 = 563;
  44.         kPrime1129 = 1129;
  45.         kPrime2267 = 2267;
  46.  
  47. {Create a new hash table. You can pass nil for the hash and match functions to use defaults.}
  48. {If you pass 0 for the average entry size, you’ll get a default data area and expansion size.}
  49. {The number of slots should be prime, since most hashing functions work best with a prime}
  50. {modulus. If you have a hashing function that doesn’t expect a prime modulus, you can set}
  51. {the number of slots as required.}
  52.     function NewHashTable (numSlots: Integer; {normally a prime number!}
  53.                                     averageEntrySize: Integer; {}
  54.                                     HashFuncPtr: ProcPtr; {function HashFunc(keyData: Ptr; keyLength, modulus: Integer): Integer;}
  55.                                     MatchFuncPtr: ProcPtr; {function MatchFunc(keyData: Ptr; keyLength: Integer; entryData: Ptr; entryLength: Integer): Boolean;}
  56.                                     var theTable: HashTable): OSErr;
  57.  
  58. {Get rid of a hash table and all its associated storage.}
  59.     function DisposeHashTable (theTable: HashTable): OSErr;
  60.  
  61. {Remove all the entries from the given table. Time is proportional to number of entries.}
  62.     function EmptyHashTable (theTable: HashTable): OSErr;
  63.  
  64. {Add a key/value pair to the table, or replace the existing value if the key is already present.}
  65. {This is optimized for speed in the case where the old and new values are the same size.}
  66.     function SetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; valuePtr: Ptr; valueLength: Integer; var replaced: Boolean): OSErr;
  67.  
  68. {Given a key, return the offset and length of the associated data within the dataBlock.}
  69.     function GetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var found: Boolean): OSErr;
  70.  
  71. {Given a key, remove its entry. This can be expensive in terms of time.}
  72.     function RemoveHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var found: Boolean): OSErr;
  73.  
  74. {Return the number of entries in the table. This is a constant-time operation.}
  75.     function CountHashEntries (theTable: HashTable; var count: Longint): OSErr;
  76.  
  77. {Enumerate all the key/value pairs in the given hash table.}
  78. {Start with state = 0. Caller must preserve state across calls.}
  79. {When state becomes 0 after call, current results are to be ignored — there’s no more.}
  80. {When state is nonzero after call, an entry is described by its offsets and lengths relative}
  81. {to the start of the dataBlock. If you add or delete an entry when state <> 0, you’ll get}
  82. {a paramErr result on the next call and state will be reset to zero.}
  83.     function GetNextHashEntry (theTable: HashTable; var keyOffset: Longint; var keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var state: Longint): OSErr;
  84.  
  85. {The efficiency of the table is defined as the number of chains divided by the number of}
  86. {buckets. With no collisions, the efficiency is 1. As the length of chains increases, the}
  87. {efficiency decreases below 1. By definition, an empty table has an efficiency of 1.}
  88. {This is a constant-time operation.}
  89.     function HashEfficiency (theTable: HashTable; var efficiency: Real): OSErr;
  90.  
  91. {The occupancy of the table is defined as the number of chains divided by the number of}
  92. {available slots. An empty table has an occupancy of 0. When all the slots have been filled,}
  93. {occupancy becomes 1. Due to collisions, it’s possible to have more entries than slots}
  94. {and still not have an occupancy of 1. This is a constant-time operation.}
  95.     function HashSlotOccupancy (theTable: HashTable; var occupancy: Real): OSErr;
  96.  
  97. {Return the number of slots allocated to this table at creation or rehash time.}
  98.     function HashTableSlotCount (theTable: HashTable; var numSlots: Integer): OSErr;
  99.  
  100. {Change the number of slots in the table. Time is proportional to number of entries.}
  101.     function ReHash (theTable: HashTable; numSlots: Integer): OSErr;
  102.  
  103. {Find out how much memory we would get back by calling CompactHashDataSpace.}
  104.     function HashRecoverableSpace (theTable: HashTable; var recoverableSpace: Size): OSErr;
  105.  
  106. {Remove gaps from the data area. Time is proportional to total size active keys and values.}
  107. {This temporarily uses additional space equal to the active data, plus an expansion reserve.}
  108. {This returns paramErr if called when HashRecoverableSpace is zero.}
  109.     function CompactHashSpace (theTable: HashTable): OSErr;
  110.  
  111. implementation
  112.  
  113.     type
  114.         TableEntryOffset = Longint;
  115.         TableEntry = record
  116.                 keyOff: Longint;
  117.                 keyLen: Integer;
  118.                 valLen: Integer;    {No offset for value; it follows key.}
  119.                 nextOffset: TableEntryOffset;    {Relative to beginning of block.}
  120.             end;
  121.         TableEntryPtr = ^TableEntry;
  122.         Table = record
  123.                 dataHandle: Handle;
  124.                 dataLimit: Longint;
  125.                 expandAmount: Size;
  126.                 bucketsHandle: Handle;
  127.                 freeBucketOffset: Longint;
  128.                 maxBuckets: Longint;
  129.                 maxIndex: Integer;
  130.                 hashFunc: ProcPtr;
  131.                 matchFunc: ProcPtr;
  132.                 iteratorSlot: Integer;
  133.                 iteratorBucket: TableEntryOffset;
  134.                 iteratorCheckCount: Longint;
  135.                 bucketCount: Longint;
  136.                 usedSlotCount: Integer;
  137.                 totalGapSpace: Size;
  138.                 slots: array[0..0] of TableEntryOffset;
  139.             end;
  140.         TablePtr = ^Table;
  141.         TableHandle = ^TablePtr;
  142.  
  143.     const
  144.         EndOfList = -1;    {In-list offsets are >= 0.}
  145.  
  146.     function DefaultHashFunction (keyData: Ptr; keyLength, modulus: Integer): Integer;
  147.         var
  148.             hashResult: Longint;
  149.             p: Ptr;
  150.             i: Integer;
  151.     begin
  152.         hashResult := 0;
  153.         p := keyData;
  154.         for i := 1 to keyLength do
  155.             begin
  156.                 hashResult := BROTR(BXOR(hashResult, p^), 5);
  157.                 p := Ptr(ORD(p) + SIZEOF(SignedByte));
  158.             end;
  159.         if hashResult < 0 then    {We need a positive result.}
  160.             hashResult := BNOT(hashResult);    {Rather than negation, because --MAXLONGINT = -MAXLONGINT.}
  161.         DefaultHashFunction := hashResult mod modulus;
  162.     end; {DefaultHashFunction}
  163.  
  164.     function DefaultMatchFunction (keyData: Ptr; keyLength: Integer; entryData: Ptr; entryLength: Integer): Boolean;
  165.     begin
  166.         DefaultMatchFunction := IUMagIDString(keyData, entryData, keyLength, entryLength) = 0;
  167.     end; {DefaultMatchFunction}
  168.  
  169.     procedure InternalLinkFreeBuckets (theTable: TableHandle; firstOffset: Longint);
  170.         var
  171.             i, nextOffset: Longint;
  172.             bucketPtr: TableEntryPtr;
  173.     begin
  174.         with theTable^^ do
  175.             begin
  176.                 bucketPtr := TableEntryPtr(ORD(bucketsHandle^) + firstOffset);
  177.                 nextOffset := firstOffset + SIZEOF(TableEntry);
  178.                 for i := (firstOffset div SIZEOF(TableEntry)) + 1 to maxBuckets - 1 do
  179.                     begin
  180.                         bucketPtr^.nextOffset := nextOffset;
  181.                         nextOffset := nextOffset + SIZEOF(TableEntry);
  182.                         bucketPtr := TableEntryPtr(ORD(bucketPtr) + SIZEOF(TableEntry));
  183.                     end;
  184.         {Tack the old free list onto the end.}
  185.                 bucketPtr^.nextOffset := freeBucketOffset;
  186.         {Put the new buckets at the head of the free list.}
  187.                 freeBucketOffset := firstOffset;
  188.             end;
  189.     end; {InternalLinkFreeBuckets}
  190.  
  191.     procedure InternalInitSlots (theTable: TableHandle);
  192.         var
  193.             entry: Integer;
  194.     begin
  195.         with TableHandle(theTable)^^ do
  196.             begin
  197.                 for entry := 0 to maxIndex do
  198.                     begin
  199. {$PUSH}
  200. {$R-}
  201.                         slots[entry] := EndOfList;
  202. {$POP}
  203.                     end;
  204.                 usedSlotCount := 0;
  205.             end;
  206.     end; {InternalInitSlots}
  207.  
  208.     function NewHashTable (numSlots: Integer; averageEntrySize: Integer; HashFuncPtr: ProcPtr; MatchFuncPtr: ProcPtr; var theTable: HashTable): OSErr;
  209.  
  210.         procedure CheckMemErr;
  211.             var
  212.                 err: OSErr;
  213.         begin
  214.             err := MemError;
  215.             if err <> noErr then
  216.                 begin
  217.                     NewHashTable := err;
  218.                     Exit(NewHashTable);
  219.                 end;
  220.         end; {CheckMemErr}
  221.  
  222.         const
  223.             MinDataBlockSize = 1024;
  224.         var
  225.             initialSize: Size;
  226.             dataBlock: Handle;
  227.             buckets: Handle;
  228.             bucketPtr: TableEntryPtr;
  229.     begin
  230.         theTable := HashTable(NewHandleClear(SIZEOF(Table) + (numSlots - 1) * SIZEOF(TableEntryOffset)));
  231.         CheckMemErr;
  232.         initialSize := averageEntrySize * numSlots;
  233.         if initialSize <= 0 then
  234.             initialSize := MinDataBlockSize;
  235.         dataBlock := NewHandle(initialSize);
  236.         CheckMemErr;
  237.         buckets := NewHandle(numSlots * SIZEOF(TableEntry));
  238.         CheckMemErr;
  239.         with TableHandle(theTable)^^ do
  240.             begin
  241.                 dataHandle := dataBlock;
  242.                 totalGapSpace := 0;
  243.                 expandAmount := initialSize div 2;
  244.                 maxIndex := numSlots - 1;
  245.                 InternalInitSlots(TableHandle(theTable));
  246.                 bucketsHandle := buckets;
  247.                 freeBucketOffset := EndOfList;    {None, yet.}
  248.                 maxBuckets := numSlots;
  249.                 InternalLinkFreeBuckets(TableHandle(theTable), 0);
  250.                 if HashFuncPtr = nil then
  251.                     hashFunc := @DefaultHashFunction
  252.                 else
  253.                     hashFunc := HashFuncPtr;
  254.                 if matchFuncPtr = nil then
  255.                     matchFunc := @DefaultMatchFunction
  256.                 else
  257.                     matchFunc := MatchFuncPtr;
  258.             end;
  259.         NewHashTable := noErr;
  260.     end; {NewHashTable}
  261.  
  262.     function EmptyHashTable (theTable: HashTable): OSErr;
  263.         var
  264.             entry: Integer;
  265.     begin
  266.         HLock(Handle(theTable));
  267.         with TableHandle(theTable)^^ do
  268.             begin
  269.                 InternalInitSlots(TableHandle(theTable));
  270.                 bucketCount := 0;
  271.                 dataLimit := 0;
  272.                 SetHandleSize(dataHandle, expandAmount * 2);
  273.                 maxBuckets := maxIndex + 1;
  274.                 SetHandleSize(bucketsHandle, maxBuckets * SIZEOF(TableEntry));
  275.                 freeBucketOffset := EndOfList;
  276.                 InternalLinkFreeBuckets(TableHandle(theTable), 0);
  277.             end;
  278.         HUnlock(Handle(theTable));
  279.         EmptyHashTable := noErr;
  280.     end; {EmptyHashTable}
  281.  
  282.     function DisposeHashTable (theTable: HashTable): OSErr;
  283.         var
  284.             err: OSErr;
  285.     begin
  286.     {Dispose all the buckets.}
  287.         DisposeHandle(TableHandle(theTable)^^.bucketsHandle);
  288.     {Dispose the dataBlock.}
  289.         DisposeHandle(TableHandle(theTable)^^.dataHandle);
  290.     {Now dispose the table itself.}
  291.         DisposeHandle(Handle(theTable));
  292.         DisposeHashTable := noErr;
  293.     end; {DisposeHashTable}
  294.  
  295.     function CallHashFunc (KeyData: Ptr; keyLength, tableMaxIndex: Integer; theCode: ProcPtr): Integer;
  296.     inline
  297.         $205F,            {movea.l (sp)+,a0}
  298.         $4E90;            {jsr (a0)}
  299.  
  300.     function CallMatchFunc (keyData: Ptr; keyLength: Integer; entryData: Ptr; entryLength: Integer; theCode: ProcPtr): Boolean;
  301.     inline
  302.         $205F,            {movea.l (sp)+,a0}
  303.         $4E90;            {jsr (a0)}
  304.  
  305. {Just like GetHashEntry, except hashKey and bucket are always returned.}
  306.     function InternalGetHashEntry (theTable: HashTable; dataBlock: Handle; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var hashKey: Integer; var bucket: TableEntryOffset): Boolean;
  307.         var
  308.             bucketPtr: TableEntryPtr;
  309.     begin
  310.         HLock(theTable);
  311.         HLock(dataBlock);
  312.         with TableHandle(theTable)^^ do
  313.             begin
  314.                 hashKey := CallHashFunc(keyPtr, keyLength, maxIndex + 1, hashFunc);
  315. {$PUSH}
  316. {$R-}
  317.                 bucket := slots[hashKey];
  318. {$POP}
  319.                 while bucket <> EndOfList do
  320.                     begin
  321.                         bucketPtr := TableEntryPtr(ORD(bucketsHandle^) + bucket);
  322.                         with bucketPtr^ do
  323.                             if CallMatchFunc(keyPtr, keyLength, Ptr(ORD(dataBlock^) + keyOff), keyLen, matchFunc) then
  324.                                 Leave
  325.                             else
  326.                                 bucket := nextOffset;
  327.                     end;
  328.                 if bucket <> EndOfList then
  329.                     with bucketPtr^ do
  330.                         begin
  331.                             valueOffset := keyOff + keyLen;
  332.                             valueLength := valLen;
  333.                             InternalGetHashEntry := True;
  334.                         end
  335.                 else
  336.                     InternalGetHashEntry := False;
  337.             end;
  338.         HUnlock(dataBlock);
  339.         HUnlock(theTable);
  340.     end; {InternalGetHashEntry}
  341.  
  342.     function InternalNewBucket (theTable: TableHandle; hashKey: Integer; keyOffset: Longint; keyLength, valueLength: Integer; var bucket: TableEntryOffset): OSErr;
  343.  
  344.         procedure CheckMemErr;
  345.             var
  346.                 err: OSErr;
  347.         begin
  348.             err := MemError;
  349.             if err <> noErr then
  350.                 begin
  351.                     InternalNewBucket := err;
  352.                     Exit(InternalNewBucket);
  353.                 end;
  354.         end; {CheckMemErr}
  355.  
  356.         var
  357.             bucketPtr: TableEntryPtr;
  358.             buckets: Handle;
  359.             oldBucketsSize: Size;
  360.             oldMaxBuckets: Longint;
  361.             bucketsExpansionCount: Integer;
  362.  
  363.     begin {InternalNewBucket}
  364.         if theTable^^.freeBucketOffset = EndOfList then
  365.             begin
  366.                 with theTable^^ do
  367.                     begin
  368.                         buckets := bucketsHandle;
  369.                         bucketsExpansionCount := maxIndex div 2;
  370.                     end;
  371.                 oldBucketsSize := GetHandleSize(buckets);
  372.                 SetHandleSize(buckets, oldBucketsSize + bucketsExpansionCount * SIZEOF(TableEntry));
  373.                 CheckMemErr;
  374.                 with theTable^^ do
  375.                     begin
  376.                         oldMaxBuckets := maxBuckets;
  377.                         maxBuckets := maxBuckets + bucketsExpansionCount;
  378.                     end;
  379.                 InternalLinkFreeBuckets(theTable, oldMaxBuckets * SIZEOF(TableEntry));
  380.             end;
  381.         with theTable^^ do
  382.             begin
  383.                 bucket := freeBucketOffset;
  384.                 bucketPtr := TableEntryPtr(ORD(bucketsHandle^) + bucket);
  385.                 freeBucketOffset := bucketPtr^.nextOffset;
  386.                 bucketCount := bucketCount + 1;
  387. {$PUSH}
  388. {$R-}
  389.                 if slots[hashKey] = EndOfList then
  390.                     usedSlotCount := usedSlotCount + 1;
  391.                 bucketPtr^.nextOffset := slots[hashKey];
  392.                 slots[hashKey] := bucket;
  393. {$POP}
  394.             end;
  395.         with bucketPtr^ do
  396.             begin
  397.                 keyOff := keyOffset;
  398.                 keyLen := keyLength;
  399.                 valLen := valueLength;
  400.             end;
  401.         InternalNewBucket := noErr;
  402.     end; {InternalNewBucket}
  403.  
  404.     function SetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; valuePtr: Ptr; valueLength: Integer; var replaced: Boolean): OSErr;
  405.  
  406.         procedure CheckMemErr;
  407.             var
  408.                 err: OSErr;
  409.         begin
  410.             err := MemError;
  411.             if err <> noErr then
  412.                 begin
  413.                     SetHashEntry := err;
  414.                     Exit(SetHashEntry);
  415.                 end;
  416.         end; {CheckMemErr}
  417.  
  418.         var
  419.             dataBlock: Handle;
  420.             newEntryOffset: Longint;
  421.  
  422.         procedure AllocateNewData;
  423.             var
  424.                 oldBlockSize, newBlockSize: Size;
  425.                 newDataLimit: Longint;
  426.         begin
  427.     {If there’s not enough room at the end of the dataBlock to hold}
  428.     {key and value, expand the block by the minimum of the expansion}
  429.     {amount or the total size of the key and value plus the expansion amount.}
  430.             oldBlockSize := GetHandleSize(dataBlock);
  431.             CheckMemErr;
  432.             with TableHandle(theTable)^^ do
  433.                 begin
  434.                     newEntryOffset := dataLimit;
  435.                     newDataLimit := dataLimit + keyLength + valueLength;
  436.                     if newDataLimit > oldBlockSize then
  437.                         begin
  438.                             if newDataLimit > oldBlockSize + expandAmount then
  439.                                 newBlockSize := newDataLimit + expandAmount
  440.                             else
  441.                                 newBlockSize := oldBlockSize + expandAmount;
  442.                             SetHandleSize(dataBlock, newBlockSize);
  443.                             CheckMemErr;
  444.                         end;
  445.                 end;
  446.             with TableHandle(theTable)^^ do
  447.                 dataLimit := newDataLimit;
  448.     {Put the new key and value into the dataBlock.}
  449.             with TableHandle(theTable)^^ do
  450.                 begin
  451.                     BlockMove(keyPtr, Ptr(ORD(dataBlock^) + newEntryOffset), keyLength);
  452.                     BlockMove(valuePtr, Ptr(ORD(dataBlock^) + newEntryOffset + keyLength), valueLength);
  453.                 end;
  454.         end; {AllocateNewData}
  455.  
  456.         var
  457.             hashKey: Integer;
  458.             oldVOff: Longint;
  459.             oldVLen: Integer;
  460.             bucket: TableEntryOffset;
  461.             bucketPtr: TableEntryPtr;
  462.  
  463.     begin {SetHashEntry}
  464.         dataBlock := TableHandle(theTable)^^.dataHandle;
  465.         if InternalGetHashEntry(theTable, dataBlock, keyPtr, keyLength, oldVOff, oldVLen, hashKey, bucket) then
  466.             begin
  467.         {There’a an old entry with this key.}
  468.                 replaced := True;
  469.                 if oldVLen <> valueLength then
  470.                     begin
  471.             {The new value is a different length than the old. Key and value get moved to the}
  472.             {end of the data area, leaving a gap. Then we update the key’s existing bucket.}
  473.                         with TableHandle(theTable)^^ do
  474.                             totalGapSpace := totalGapSpace + keyLength + oldVLen;
  475.                         AllocateNewData;
  476.                         with TableHandle(theTable)^^, TableEntryPtr(ORD(bucketsHandle^) + bucket)^ do
  477.                             begin
  478.                                 keyOff := newEntryOffset;
  479.                                 valLen := valueLength;
  480.                             end;
  481.                     end
  482.                 else
  483.                     begin
  484.             {The new value overlays the old value of the same length.}
  485.                         BlockMove(valuePtr, Ptr(ORD(dataBlock^) + oldVOff), oldVLen);
  486.                     end;
  487.             end
  488.         else
  489.             begin
  490.         {This is the first time we’ve seen this key. Store key and value in the data area}
  491.         {and give them a new bucket.}
  492.                 replaced := False;
  493.                 AllocateNewData;
  494.                 SetHashEntry := InternalNewBucket(TableHandle(theTable), hashKey, newEntryOffset, keyLength, valueLength, bucket);
  495.             end;
  496.         SetHashEntry := noErr;
  497.     end; {SetHashEntry}
  498.  
  499.     function RemoveHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var found: Boolean): OSErr;
  500.         var
  501.             dataBlock: Handle;
  502.             hashKey: Integer;
  503.             bucket: TableEntryOffset;
  504.             bucketPtr, chainFollower: TableEntryPtr;
  505.             existingValueOffset: Longint;
  506.             existingValueLength: Integer;
  507.             ignoreLong: Longint;
  508.     begin
  509.         dataBlock := TableHandle(theTable)^^.dataHandle;
  510.         if InternalGetHashEntry(theTable, dataBlock, keyPtr, keyLength, existingValueOffset, existingValueLength, hashKey, bucket) then
  511.             with TableHandle(theTable)^^ do
  512.                 begin
  513.         {Unlink the bucket.}
  514.                     bucketPtr := TableEntryPtr(ORD(bucketsHandle^) + bucket);
  515.                     bucketCount := bucketCount - 1;
  516. {$PUSH}
  517. {$R-}
  518.                     chainFollower := TableEntryPtr(ORD(bucketsHandle^) + slots[hashKey]);
  519. {$POP}
  520.                     if chainFollower = bucketPtr then {Delete first bucket.}
  521.                         begin
  522.                             if bucketPtr^.nextOffset = EndOfList then
  523.                                 usedSlotCount := usedSlotCount - 1;
  524. {$PUSH}
  525. {$R-}
  526.                             slots[hashKey] := bucketPtr^.nextOffset;
  527. {$POP}
  528.                         end
  529.                     else
  530.                         begin
  531.                             while chainFollower^.nextOffset <> bucket do
  532.                                 chainFollower := TableEntryPtr(ORD(bucketsHandle^) + chainFollower^.nextOffset);
  533.                             chainFollower^.nextOffset := bucketPtr^.nextOffset;
  534.                         end;
  535.             {Account for the unused space.}
  536.                     with bucketPtr^ do
  537.                         totalGapSpace := totalGapSpace + keyLen + valLen;
  538.         {Link the disposed bucket onto the free list.}
  539.                     bucketPtr^.nextOffset := freeBucketOffset;
  540.                     freeBucketOffset := bucket;
  541.                     found := True;
  542.                 end
  543.         else
  544.             found := False;
  545.         RemoveHashEntry := noErr;
  546.     end; {RemoveHashEntry}
  547.  
  548.     function GetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var found: Boolean): OSErr;
  549.         var
  550.             hashKey: Integer;
  551.             bucket: TableEntryOffset;
  552.     begin
  553.         dataBlock := TableHandle(theTable)^^.dataHandle;
  554.         found := InternalGetHashEntry(theTable, dataBlock, keyPtr, keyLength, valueOffset, valueLength, hashKey, bucket);
  555.         GetHashEntry := noErr;
  556.     end; {GetHashEntry}
  557.  
  558.     function CountHashEntries (theTable: HashTable; var count: Longint): OSErr;
  559.     begin
  560.         count := TableHandle(theTable)^^.bucketCount;
  561.         CountHashEntries := noErr;
  562.     end; {CountHashEntries}
  563.  
  564.     procedure InternalAdvanceIterator (theTable: TableHandle; firstTime: Boolean);
  565.     begin
  566.         with theTable^^ do
  567.             begin
  568.                 if firstTime then
  569.                     begin
  570.                         iteratorBucket := EndOfList;    {Start at the beginning.}
  571.                         iteratorSlot := -1;
  572.                         iteratorCheckCount := bucketCount;
  573.                     end;
  574.                 while (iteratorBucket = EndOfList) and (iteratorSlot < maxIndex) do
  575.                     begin
  576.                         iteratorSlot := iteratorSlot + 1;
  577. {$PUSH}
  578. {$R-}
  579.                         iteratorBucket := slots[iteratorSlot];
  580. {$POP}
  581.                     end;
  582.             end;
  583.     end; {InternalAdvanceIterator}
  584.  
  585.     function GetNextHashEntry (theTable: HashTable; var keyOffset: Longint; var keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var state: Longint): OSErr;
  586.         var
  587.             bucketPtr: TableEntryPtr;
  588.     begin
  589.         with TableHandle(theTable)^^ do
  590.             if (state <> 0) and (bucketCount <> iteratorCheckCount) then
  591.                 begin
  592.                     GetNextHashEntry := paramErr;
  593.                     state := 0;
  594.                     Exit(GetNextHashEntry);
  595.                 end;
  596.         InternalAdvanceIterator(TableHandle(theTable), state = 0);
  597.         with TableHandle(theTable)^^ do
  598.             if iteratorBucket <> EndOfList then
  599.                 begin
  600.                     bucketPtr := TableEntryPtr(ORD(bucketsHandle^) + iteratorBucket);
  601.                     with bucketPtr^ do
  602.                         begin
  603.                             keyOffset := keyOff;
  604.                             keyLength := keyLen;
  605.                             valueOffset := keyOff + keyLen;
  606.                             valueLength := valLen;
  607.                         end;
  608.                     state := state + 1;
  609.                     iteratorBucket := bucketPtr^.nextOffset;
  610.                 end
  611.             else
  612.                 state := 0;    {No more.}
  613.         dataBlock := TableHandle(theTable)^^.dataHandle;
  614.         GetNextHashEntry := noErr;
  615.     end; {GetNextHashEntry}
  616.  
  617.     function HashEfficiency (theTable: HashTable; var efficiency: Real): OSErr;
  618.     begin
  619.         with TableHandle(theTable)^^ do
  620.             if bucketCount > 0 then
  621.                 efficiency := usedSlotCount / bucketCount
  622.             else
  623.                 efficiency := 1.0;
  624.         HashEfficiency := noErr;
  625.     end; {HashEfficiency}
  626.  
  627.     function HashSlotOccupancy (theTable: HashTable; var occupancy: Real): OSErr;
  628.     begin
  629.         with TableHandle(theTable)^^ do
  630.             occupancy := usedSlotCount / (maxIndex + 1);
  631.         HashSlotOccupancy := noErr;
  632.     end; {HashSlotOccupancy}
  633.  
  634.     function HashTableSlotCount (theTable: HashTable; var numSlots: Integer): OSErr;
  635.     begin
  636.         with TableHandle(theTable)^^ do
  637.             numSlots := maxIndex + 1;
  638.         HashTableSlotCount := noErr;
  639.     end; {HashTableSlotCount}
  640.  
  641.     function ReHash (theTable: HashTable; numSlots: Integer): OSErr;
  642.  
  643.         procedure CheckMemErr;
  644.             var
  645.                 err: OSErr;
  646.         begin
  647.             err := MemError;
  648.             if err <> noErr then
  649.                 begin
  650.                     ReHash := err;
  651.                     Exit(ReHash);
  652.                 end;
  653.         end; {CheckMemErr}
  654.  
  655.         var
  656.             rehashChain, chainTemp: TableEntryOffset;
  657.             rehashChainPtr: TableEntryPtr;
  658.             slotIndex: Integer;
  659.  
  660.     begin {ReHash}
  661.     {Collect all the entries into one long chain.}
  662.         rehashChain := EndOfList;
  663.         InternalAdvanceIterator(TableHandle(theTable), True);
  664.         with TableHandle(theTable)^^ do
  665.             begin
  666.                 repeat
  667.                     if iteratorBucket <> EndOfList then
  668.                         begin
  669.                             chainTemp := rehashChain;
  670.                             rehashChain := iteratorBucket;
  671.                             rehashChainPtr := TableEntryPtr(ORD(bucketsHandle^) + iteratorBucket);
  672.                             iteratorBucket := TableEntryPtr(ORD(bucketsHandle^) + iteratorBucket)^.nextOffset;
  673.                             rehashChainPtr^.nextOffset := chainTemp;
  674.                         end;
  675.                     InternalAdvanceIterator(TableHandle(theTable), False);
  676.                 until iteratorBucket = EndOfList;
  677.             end;
  678.     {Resize the table.}
  679.         with TableHandle(theTable)^^ do
  680.             begin
  681.                 maxIndex := numSlots - 1;
  682.                 SetHandleSize(theTable, SIZEOF(Table) + maxIndex * SIZEOF(TableEntryOffset));
  683.                 CheckMemErr;
  684.             end;
  685.     {Clear the slots.}
  686.         InternalInitSlots(TableHandle(theTable));
  687.     {Rehash each entry and chain it into its new slot, and update occupancy info.}
  688.         HLock(theTable);
  689.         with TableHandle(theTable)^^ do
  690.             begin
  691.                 HLock(dataHandle);
  692.                 HLock(bucketsHandle);
  693.                 rehashChainPtr := TableEntryPtr(ORD(bucketsHandle^) + rehashChain);
  694.                 while rehashChain <> EndOfList do
  695.                     begin
  696.                         with rehashChainPtr^ do
  697.                             slotIndex := CallHashFunc(Ptr(ORD(dataHandle^) + keyOff), keyLen, numSlots, hashFunc);
  698.                         chainTemp := rehashChainPtr^.nextOffset;
  699. {$PUSH}
  700. {$R-}
  701.                         if slots[slotIndex] = EndOfList then
  702.                             usedSlotCount := usedSlotCount + 1;
  703.                         rehashChainPtr^.nextOffset := slots[slotIndex];
  704.                         slots[slotIndex] := rehashChain;
  705. {$POP}
  706.                         rehashChain := chainTemp;
  707.                         rehashChainPtr := TableEntryPtr(ORD(bucketsHandle^) + rehashChain);
  708.                     end;
  709.                 HUnlock(bucketsHandle);
  710.                 HUnlock(dataHandle);
  711.             end;
  712.         HUnlock(theTable);
  713.         ReHash := noErr;
  714.     end; {ReHash}
  715.  
  716.     function HashRecoverableSpace (theTable: HashTable; var recoverableSpace: Size): OSErr;
  717.     begin
  718.         with TableHandle(theTable)^^ do
  719.             begin
  720.                 recoverableSpace := GetHandleSize(dataHandle) - ((dataLimit - totalGapSpace) + expandAmount);
  721.                 if recoverableSpace <= expandAmount then
  722.                     recoverableSpace := 0;
  723.             end;
  724.         HashRecoverableSpace := noErr;
  725.     end; {HashRecoverableSpace}
  726.  
  727.     function CompactHashSpace (theTable: HashTable): OSErr;
  728.  
  729.         procedure CheckErr (err: OSErr);
  730.         begin
  731.             if err <> noErr then
  732.                 begin
  733.                     CompactHashSpace := err;
  734.                     Exit(CompactHashSpace);
  735.                 end;
  736.         end; {CheckErr}
  737.  
  738.         var
  739.             ignoreErr: OSErr;
  740.             recoverableSpace: Size;
  741.             newSize: Size;
  742.             newDataBlock: Handle;
  743.             newDataLimit: Longint;
  744.             entrySize: Size;
  745.  
  746.     begin {CompactHashSpace}
  747.     {Bail out if it’s not worth the effort.}
  748.         ignoreErr := HashRecoverableSpace(theTable, recoverableSpace);
  749.         if recoverableSpace = 0 then
  750.             CheckErr(paramErr);
  751.     {Create a new data area of the requisite size.}
  752.         with TableHandle(theTable)^^ do
  753.             newSize := dataLimit - totalGapSpace + expandAmount;
  754.         newDataBlock := NewHandle(newSize);
  755.         CheckErr(MemError);
  756.     {Copy each live entry from the old area to the new, then set its bucket to the copied data.}
  757.         newDataLimit := 0;
  758.         InternalAdvanceIterator(TableHandle(theTable), True);
  759.         with TableHandle(theTable)^^ do
  760.             repeat
  761.                 if iteratorBucket <> EndOfList then
  762.                     with TableEntryPtr(ORD(bucketsHandle^) + iteratorBucket)^ do
  763.                         begin
  764.                             entrySize := keyLen + valLen;
  765.                             BlockMove(Ptr(ORD(dataHandle^) + keyOff), Ptr(ORD(newDataBlock^) + newDataLimit), entrySize);
  766.                             keyOff := newDataLimit;
  767.                             newDataLimit := newDataLimit + entrySize;
  768.                             iteratorBucket := nextOffset;
  769.                         end;
  770.                 InternalAdvanceIterator(TableHandle(theTable), False);
  771.             until iteratorBucket = EndOfList;
  772.     {Get rid of the old data area and install the new, then update housekeeping info.}
  773.         DisposeHandle(TableHandle(theTable)^^.dataHandle);
  774.         with TableHandle(theTable)^^ do
  775.             begin
  776.                 dataHandle := newDataBlock;
  777.                 dataLimit := newDataLimit;
  778.                 totalGapSpace := 0;
  779.             end;
  780.         CompactHashSpace := noErr;
  781.     end; {CompactHashSpace}
  782.  
  783. end.